package com.openess.bigsearch.engine.index;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import com.openess.bigsearch.engine.BTree.util.ByteArrays;
import com.openess.bigsearch.engine.document.Field;

/**
 * 倒排项的封装类，主要包装(id, ffrqList, posList)。
 * 为了实现BM25F模型，现在将倒排项进行修改，修改内容：加入关键域的词频，只统计关键域的词频
 * ，而不是所有的域。目前的设计统计如下几种域：title,keyword
 * ,Description,content。有的分词因为仅在非关键域中出现过，所以其对应的ffrqList为空，这种情况是可能出现的。
 * 
 * @author TuTu
 * 
 */
public class FrqPos implements Comparable<FrqPos> {
	/**
	 * 头数据所占字节数，目前头数据只保存词项的出现次数，利用这个数据可以计算出写入磁盘时会占用的字节数(不包括头信息的字节数)
	 */
	public static final int HEADSIZE = 4;
	/**
	 * 分词所在文档ID
	 */
	private long id;
	/**
	 * 分词在这篇文档中的出现频率，即出现次数
	 */
	private int frq;
	/**
	 * 关键域的词频
	 */
	private Integer[] ffrqList;
	/**
	 * 分词在文档中的位置信息
	 */
	private Integer[] pos;
	/**
	 * FrqPos对象中保存数据所占的字节数，包括：id,frqList,posList，但不包括头信息
	 */
	private int size;
	/**
	 * 将数据转换成字节数组，准备写入磁盘，不包括头信息
	 */
	private byte[] data;
	/**
	 * 头信息，目前只包括FrqPos对象所占的字总数
	 */
	private byte[] head;

	public FrqPos() {
	}

	public FrqPos(long id, Integer[] ffrqList, Integer[] pos) {
		this.id = id;
		this.pos = pos;
		this.frq = pos.length;
		this.size = 8 + Field.MFSIZE * 4 + pos.length * 4;
		this.data = new byte[this.size];
		this.ffrqList = ffrqList;
	}

	/**
	 * FrqPos对象中保存数据所占的字节数，包括：id,frq,pos，但不包括头信息
	 * 
	 * @return
	 */
	public final int getSize() {
		return size;
	}

	public final long getId() {
		return id;
	}

	public final int getFrq() {
		return frq;
	}

	public final Integer[] getPos() {
		return pos;
	}

	public final Integer[] getFfrqList() {
		return ffrqList;
	}

	/**
	 * 往磁盘中写入数据，这个方法主要供BPlusTree调用
	 * 
	 * @param cf
	 * @param offset
	 * @throws IOException
	 */
	public void write(FileChannel cf, long offset) throws IOException {
		// 头数据包括数据所占字节数
		writeHead(cf, offset);

		ByteArrays.putLong(id, data, 0);
		for (int i = 0; i < ffrqList.length; i++) {
			Integer ffrq = ffrqList[i];
			if (ffrq != null)
				ByteArrays.putInt(ffrq, data, 8 + i * 4);
		}
		int j = 0;
		for (int p : pos) {
			ByteArrays.putInt(p, data, 8 + Field.MFSIZE * 4 + j * 4);
			j++;
		}
		ByteBuffer buf = ByteBuffer.wrap(data);
		cf.write(buf, offset + HEADSIZE);
	}

	/**
	 * 往data数组中放入头信息，待所有数据都放入data后，一起写入。头信息保存内容包括：1.分词频率即pos的个数
	 * 
	 * @param cf
	 * @param offset
	 * @throws IOException
	 */
	private void writeHead(FileChannel cf, long offset) throws IOException {
		head = new byte[HEADSIZE];
		ByteArrays.putInt(frq, head, 0);
		ByteBuffer buf = ByteBuffer.wrap(head);
		cf.write(buf, offset);
	}

	/**
	 * 从磁盘中读取数据
	 * 
	 * @param cf
	 * @param offset
	 * @return
	 * @throws IOException
	 */
	public FrqPos read(FileChannel cf, long offset) throws IOException {
		readHead(cf, offset);
		// 从磁盘读取数据
		data = new byte[size];
		ByteBuffer buf = ByteBuffer.wrap(data);
		cf.read(buf, offset + HEADSIZE);

		// 给变量赋值
		id = ByteArrays.getLong(data, 0);
		ffrqList = new Integer[Field.MFSIZE];
		for (int i = 0; i < Field.MFSIZE; i++) {
			ffrqList[i] = ByteArrays.getInt(data, 8 + i * 4);
		}
		pos = new Integer[frq];
		for (int i = 0; i < frq; i++) {
			pos[i] = ByteArrays.getInt(data, 8 + Field.MFSIZE * 4 + i * 4);
		}
		return this;
	}

	/**
	 * 从磁盘中读取出头信息，给变量head,size
	 * 
	 * @param cf
	 * @param offset
	 * @throws IOException
	 */
	public void readHead(FileChannel cf, long offset) throws IOException {
		head = new byte[HEADSIZE];
		ByteBuffer buf = ByteBuffer.wrap(head);
		cf.read(buf, offset);
		frq = ByteArrays.getInt(head, 0);
		// docID+frq*pos
		size = 8 + Field.MFSIZE * 4 + frq * 4;
	}

	@Override
	public int compareTo(FrqPos o) {
		long result = this.id - o.id;
		if (result > 0) {
			return 1;
		} else if (result < 0) {
			return -1;
		} else {
			return 0;
		}
	}

	@Override
	public String toString() {
		String str = id + ":(";
		for (int i = 0; i < ffrqList.length - 1; i++) {
			str += ffrqList[i] + ",";
		}
		str += ffrqList[ffrqList.length - 1] + "), (";
		for (int i = 0; i < pos.length - 1; i++) {
			str += pos[i] + ",";
		}
		return str + pos[pos.length - 1] + ")";
	}
}
